<?php

/**
 * parseMe
 *
 * An extra light RSS/Atom parser you can use on
 * pretty much any web enabled device. The application
 * is focused on small screen and/or accessibility devices.
 * 
 * @version $Id: index.php 2 2006-12-15 04:15:28Z epsi $
 * @author  $Author: epsi $
 */


### START USER CONFIGURABLE OPTIONS ###########################################

// Application name, for display

$appName = 'parseMe';

// Define the location and state of the cache directory

$cacheDir = './cache';

// Define the local cache "time to live".

$cacheTTL = '30 minutes';

// Pre-defined xml sources, for easy manipulation and to bypass
// the url length limitations of some devices (phones, etc).

// Remember that some devices might not display this list as
// a standard browser drop down list, but more lie a bulleted
// list, so it is advised to keep it rather short (< 10) to
// avoid killer scrolling on on small screens (phones, etc).

$xmlSourceList = array('Slashdot'    => 'http://rss.slashdot.org/Slashdot/slashdot',
                       'Digg'        => 'http://www.digg.com/rss/index.xml',
                       'Google News' => 'http://news.google.com/?output=rss',
                       'tekArtist'   => 'http://feeds.feedburner.com/tekArtist');

// Define a default number of item per page

// Start with a small number as a default view, because many
// small devices have drastic memory requirements that lead
// to easily lead to http errors (413: Request entity too large)

$defaultLimit = 5;

### END USER CONFIGURABLE OPTIONS #############################################


$defaultOffset = 0;

// start the xhtml interface

print '<'.'?xml version="1.0"?'.">\n";
print "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n";
print "    \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
print "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n";
print "\n";

// Define a default xml source key

$sourceKeys = array_keys($xmlSourceList);
$defaultSource = $sourceKeys[0];

// Get and clean user input

$sourceChoice = rawurldecode($_GET['src']);
$limit        = intval($_GET['lmt']);
$offset       = intval($_GET['ofst']);

// If no valid source key choice, then use default

if($sourceChoice == ''){
    $sourceChoice = $defaultSource;
}
elseif(!isset($xmlSourceList[$sourceChoice])){
    $sourceChoice = $defaultSource;
}

// Get the url of the chosen source

$xmlSource = $xmlSourceList[$sourceChoice];

// Cleanup the source key for display in the header

$cleanSourceChoice = htmlspecialchars($sourceChoice);

// Set page title and header

$title .= $appName.' &raquo; '.$cleanSourceChoice;

// Cleanup the selected page offset and item limit

if( ($limit < 1) or ($limit > 25) ) $limit = $defaultLimit;

if( ($offset < 1) or ($offset > 100) ) $offset = $defaultOffset;

// Print head block w/ defined and clean info

print "    <head>\n";
print "        <title>{$title}</title>\n";
print "        <style type=\"text/css\" media=\"screen\" title=\"Default\">@import \"lib/css/inc-all.css\";</style>\n";
print "        <style type=\"text/css\" media=\"handheld\" title=\"Handheld\">@import \"lib/css/inc-handheld.css\";</style>\n";
print "        <meta http-equiv=\"Content-Language\" content=\"en\" />\n";
print "    </head>\n";
print "    <body>\n";

if(!file_exists($cacheDir)){
    print "       <p><strong>Sorry, but the cache directory cannot be found.</strong></p>\n";
    print "       <p>Please create a writable directory at {$cacheDir}.</p>\n";
}
elseif(!is_dir($cacheDir)){
    print "       <p><strong>Sorry, but the cache location is not a directory.</strong></p>\n";
    print "       <p>Please create a writable directory at {$cacheDir}.</p>\n";
}
elseif(!is_writable($cacheDir)){
    print "       <p><strong>Sorry, but the cache directory is not writable.</strong></p>\n";
    print "       <p>Please make the directory writable: {$cacheDir}.</p>\n";
}
else{

    // Define the name and location of the local xml cache file
    
    $cacheFileName = str_replace('%','_',rawurlencode($xmlSource)).'.cache.xml';
    $cacheDestination = "./cache/{$cacheFileName}";
    
    // Set the cache creation/modification time
    
    $cacheMtime = time();
    
    // Define if we should get new content from the source,
    // or use the local cache instead
    
    if(!file_exists($cacheDestination)){
        file_put_contents($cacheDestination, file_get_contents($xmlSource));
    }
    elseif(filemtime($cacheDestination) < strtotime($cacheTTL.' ago')){
        file_put_contents($cacheDestination, file_get_contents($xmlSource));
    }
    else{
        $cacheMtime = filemtime($cacheDestination);
    }
    
    // Load and test xml content from RSS or Atom feed
    
    if(($xml = simplexml_load_file($cacheDestination)) === false){
    
        print "       <p><strong>Sorry, but \"{$cleanSourceChoice}\" cannot be parsed at this time.</strong></p>\n";
    
    }
    else{
        
        // Use the ParseFeed function (see below) to parse
        // whichever xml format we got into a clean, standard
        // structure, taking in consideration the current
        // paging preferences (offset, limit)
        
        $displayData = ParseFeed($xml);
        
        // Print the file body header
        
        print "        <h1><a href=\"{$xmlSource}\" target=\"_blank\">"
             ."<img id=\"pmRssIcon\" src=\"./lib/img/feed-icon16x16.gif\" alt=\"XML\" width=\"16\" height=\"16\" hspace=\"0\" vspace=\"0\" border=\"0\" />"
             ."</a> <a href=\"".$displayData['feedLink']."\" target=\"_blank\">"
             .$displayData['feedTitle']."</a> "
             ."</h1>\n";
        
        // If we have any, loop on the entries
        
        if(count($displayData['feedEntries']) > 0){
            print "        <dl>\n";
            foreach($displayData['feedEntries'] as $entry){
                print "            <dt><a href=\"".$entry['link']."\" target=\"_blank\">".$entry['title'] . "</a></dt>\n";
                print "            <dd>".$entry['description']."</dd>\n";
            }
            print "        </dl>\n";
        }
        
        // Deal with the paging toolbar
        
        if($displayData['entryCount'] > $limit){
        
            print "        <p>| \n";
    
            $link = $_SERVER['REQUEST_URI'];
            
            if(strstr($link, 'ofst=') === false) $link .= '?';
        
            // "Previous" link
            
            if($offset > 0){
                if(strstr($link, 'ofst=')) $link = preg_replace('/(\&ofst=\d{1,2})/','',$link);
                $link .= '&ofst='.($offset - $limit);
                print "            <a href=\"".htmlentities($link)."\">&laquo; previous</a>\n";
            }
            
            // "Next" link
            
            if($displayData['entryCount'] > ($limit + $offset)){
                if($offset > 0) print "            | \n";
                if(strstr($link, 'ofst=')) $link = preg_replace('/(\&ofst=\d{1,2})/','',$link);
                $link .= '&ofst='.($limit + $offset);
                print "            <a href=\"".htmlentities($link)."\">next &raquo;</a>\n";
            }
    
            print "        |</p>\n";
        
        }
        
        // Print the cache specs
        
        print "        <p id=\"pmFooterMeta\">\n";
        print "            {$cleanSourceChoice} XML cached on ".date('Y-m-d \a\t H:i:s T',$cacheMtime)."<br />\n";
        print "            ".date('i \m\i\n. s \s\e\c.',( 1800 - (time() - $cacheMtime) ))." to refresh.<br />\n";
        print "        </p>\n";
        
    }
    
    // Now let's display the preference form
    
    // List the available sources
    
    print "        <form action=\"./\" method=\"get\">\n";
    print "            <select name=\"src\">\n";
    
    foreach($xmlSourceList as $sourceName => $sourceURL){
        $selectedSource = '';
        if($sourceName == $sourceChoice){
            $selectedSource = 'selected="selected"';
        }
        print "                <option value=\"{$sourceName}\" {$selectedSource}>{$sourceName}</option>\n";
    }
    
    print "            </select>\n";
    
    // List the available items per page limits
    
    print "            <select name=\"lmt\">\n";
    print "                <option>1</option>\n";
    
    for($i=5; $i<=25; $i+=5){
        $selectedLimit = '';
        if($i == $limit){
            $selectedLimit = 'selected="selected"';
        }
        print "                <option value=\"{$i}\" {$selectedLimit}>{$i}</option>\n";
    }
    
    print "            </select>\n";
    
    // And a nice submit button
    
    print "            <input type=\"submit\" name=\"s\" value=\"Go!\" />\n";
    print "        </form>\n";

}

// Close the xhtml file

print "    </body>\n";
print "</html>\n";

// And we're done with the processing!

exit;




// Now let's move on to the parsing functions

/**
 * ParseFeed is a simple decisional wrapper. It simply
 * defines type of feed we are dealing with (RSS or Atom)
 * and calls the approriate xml parser.
 *
 * @param  object $xml          SimpleXML object (http://php.net/simplexml)
 * @return array  $displayData  Associative array to be used for display
 *
 * $displayData => feedTitle
 * ............ => feedLink
 * ............ => feedEntries => idx => link
 * .................................. => title
 * .................................. => description
 * ............ => entryCount
 */ 

function ParseFeed(&$xml){

    $displayData = array();
    
    if(isset($xml->channel)){
        ParseRSS($xml, $displayData);
    }
    elseif(isset($xml->entry)){
        ParseAtom($xml, $displayData);
    }
    
    return $displayData;

}

/**
 * ParseRSS, as the name implies, will parse an RSS
 * feed into the standard array we expect for display.
 * See http://en.wikipedia.org/wiki/RSS_%28file_format%29
 *
 * @param  object $xml          RSS, SimpleXML object (http://php.net/simplexml)
 * @param  array  $displayData  Empty array, see ParseFeed
 * @global int    $limit        Only list x items per page
 * @global int    $offset       Start listing from item number x
 * @return array  $displayData  Associative array to be used for display
 */ 

function ParseRSS(&$xml, &$displayData){
    
    global $limit, $offset;
    
    // Get the feed basic info
    
    $displayData['feedTitle'] = $xml->channel->title;
    $displayData['feedLink']  = $xml->channel->link;

    $displayData['feedEntries'] = array();
    $displayData['entryCount'] = 0;

    // Test for the doc entries structure, depending on
    // the RSS version (items in/outside of channel)
    
    if(isset($xml->item)){
        $feedEntries = $xml->item;
    }
    elseif(isset($xml->channel->item)){
         $feedEntries = $xml->channel->item;
    }
    else{
        $feedEntries = false;
    }
    
    // Loop on the found entries, based on paging prefs

    if(is_object($feedEntries)){
        $displayData['entryCount'] = count($feedEntries);
        $i=0;
        foreach($feedEntries as $entry){
            if($i >= $offset){
                $displayData['feedEntries'][$i] = array();
                $displayData['feedEntries'][$i]['link'] = $entry->link;
                $displayData['feedEntries'][$i]['title'] = $entry->title;
                $displayData['feedEntries'][$i]['description'] = substr(strip_tags(str_replace("\n",' ', $entry->description)),0,1025);
                if(strlen($entry->description) > 1024) $displayData['feedEntries'][$i]['description'] .= '...';
            }
            $i++;
            if($i >= ($limit + $offset)) break;
        }
    }

	return $displayData;

}

/**
 * ParseAtom, as the name implies, will parse an Atom
 * feed into the standard array we expect for display.
 * See http://en.wikipedia.org/wiki/Atom_%28standard%29
 *
 * @param  object $xml          Atom, SimpleXML object (http://php.net/simplexml)
 * @param  array  $displayData  Empty array, see ParseFeed
 * @global int    $limit        Only list x items per page
 * @global int    $offset       Start listing from item number x
 * @return array  $displayData  Associative array to be used for display
 */ 

function ParseAtom(&$xml, &$displayData){
    
    global $limit, $offset;
    
    // Get the feed basic info
    
    $displayData['feedTitle'] = $xml->title;
    
    $feedLinkAttributes = $xml->link->attributes();
    $displayData['feedLink']  = $feedLinkAttributes['href'];

    $displayData['feedEntries'] = array();
    $displayData['entryCount'] = 0;

    // Test for the doc entries structure

    if(isset($xml->entry)){
        $feedEntries = $xml->entry;
    }
    else{
        $feedEntries = false;
    }
    
    // Loop on the found entries, based on paging prefs

    if(is_object($feedEntries)){
        $displayData['entryCount'] = count($feedEntries);
        $i=0;
        foreach($feedEntries as $entry){
            if($i >= $offset){
                $displayData['feedEntries'][$i] = array();
                $entryAttributes = $entry->link->attributes();
                $displayData['feedEntries'][$i]['link'] = $entryAttributes['href'];
                $displayData['feedEntries'][$i]['title'] = $entry->title;
                $displayData['feedEntries'][$i]['description'] = substr(strip_tags(str_replace("\n",' ', $entry->content)),0,1025);
                if(strlen($displayData['feedEntries'][$i]['description']) > 1024) $displayData['feedEntries'][$i]['description'] .= strlen($entry->content).'...';
            }
            $i++;
            if($i >= ($limit + $offset)) break;
        }
    }

	return $displayData;

}

// Fin

?>